/**
 * MedryxORM is a javascript based, client-side lightweight client-server mapping system. 
 * It provides a mechanism to define simple javascript objects (SJO's) that can be initialized with json
 * arrays, and handles all the initialization for the SJO.
 * 
 * The real power of this ORM comes in its ability to automatically handle lazy association fetching,
 * and sparsely-populated objects. This makes use of the SJO's simple and transparent to the end 
 * user of the SJO. In addition, this allows for easy optimization of client performance by tuning
 * of server responses, without any change to client side code. For example, an initial version
 * of a server service may simply return a json entity without any of its relationships. These relationships
 * would then be automatically initiated at run-time when htey were requested (default lazy fetching).
 * However, if a particular path was being used more frequently, then the server could send down
 * a more richly populated object, which would in essence disable the lazy fetching and load associations
 * and properties eagerly.
 * 
 * In order to provide this degree of "instrumentation" of the SJO's, accessors and mutators must be used
 * to get or set property values in the SJO. Note that these are provided to the SJO automatically, so
 * that the SJO writer need not be bothered with writing these tedious methods manually.
 * 
 * SJO also provides dirty checking for properties of SJO's. This facilitates updates that can be posted to 
 * a server. [CASCADING IS NOT YET SUPPORTED]
 * 
 * SJO's are simple javascript objects that can be easily unit tested once they are instrumented (to add in
 * accessors and mutators). Note that even in "internal" methods that access properties, accessors and mutators
 * should be used to facilitate lazy loading. 
 * 
 * There are two ways to provide the "mapping information" needed by the EntityManager for an SJO. Either
 * use a JSON mapping file, or declare the associations in the object itself. The mappings are declared within
 * an SJO as:
 * 			
 * 				propertyName:new [PropertyType](kwArguments)
 * 
 * PropertyType can be:
 * 		1. medryx.orm.Property: use for simple properites (string, integer, date, etc. -- anything that isn't an "object")
 * 		2. medryx.orm.Association: use for associated objects.
 * 		3. medryx.orm.Collection
 *      4. medryx.orm.Identifier
 *      
 *      //5. medryx.orm.Component -- not yet implemented
 * Arguments:
 * 		entityClass: required for Association or Collection
 * 		identifier:  name of the identifier property, defaults to "id"
 * 		lazy: true by default. if false, then if this property is not initialized at object creation time, it is fetched immediately. o
 * 			  otherwise, it is only fetched when a call to the property's accessor is made
 * 		cascade: NOT YET SUPPORTED
 * 
 * if provided as JSON:
 * 			propertyName:{
 * 				propertyType:(String or class) medryx.orm.Identifier | medryx.orm.Property | medryx.orm.Association | medryx.orm.Collection,
 * 				attributes:kwArguments as above
 * 			}
 * 
 *  To put an entityClass under the control of the EntityManager it is "registered" with the factory as:
 *  		EntityManager.register(entityClass, lazyService (optional), mapping (optional), alias (optioonal))
 *  
 *  You can also register multiple entities:
 *  		EntityManager.registerAll([{
 *  					entityClass:entityClass, 
 *  					persistenceService: persistenceService (optional), 
 *  					mapping:jsonObjectOrString (optional) 
 *                      alias:a short name for this entityClass. must still be unique in this factory
 *                              useful for things like fetch, so you can specify entityClass:"Patient"
 *                              instead of entityClass:"medryx.model.Patient"
 *  }]);
 *  
 *  
 *  To build an instance of an object managed by the EntityManager, it must be instantiated through the factory as:
 *  		EntityManager.createEntity(entityClass, arguments)
 *  
 *  Finally, you need to define your lazy-loading service for the factory if you have any entities for which
 *  you have not provided a lazyService. This is any class that implements GenericPersistenceService.
 *  
 *  
 *  INTERNALS
 *  
 *  When an entity is instantiated with its arguments, the arguments are first mixed in to the entity. If a property
 *  that is an association has been mixed in with an integer, then a stub for that entity is created with the provided
 *  id. If an association has been mixed in with an object, then that associated entity is instantiated with the
 *  object. If a collection is mixed in with an array, then each member of the array that is in an integer will
 *  be replaced with a stub of the entityClass with the id as the integer. If the member of the array is an object
 *  then the member will be replaced with the entityClass initialized with the object.
 *  
 *  Note that when an associaton/collection-member is initialized with an object, the object must have its identifier
 *  property set to a non-null value. A null id is an error condition and will throw an exception.
 *  
 *  After mixing in the initializing arguments, then any "eager" properties are fetched (i.e. lazy:false in the mapping)
 *  
 *  The object is now ready for use. If a property has not been initialized then on the first call to its accessor it will 
 *  attempt to lazy load the value.
 *  
 *  SYNCHRONOUS VS ASYNCHRONOUS LAZY FETCHING
 *  
 *  For ease of coding, use of the simple "get" accessor triggers a SYNCHRONOUS LAZY FETCH -- the application will wait
 *  indefinitely (or until an error is thrown) for the value to be loaded. This may not always be desirable. Therefore
 *  every property can also be accessed asynchronously, with an async-accessor: "get" + PropertyName + "Async". this
 *  will always return a Deferred.
 *  
 *  If you know you will be using a particular properties of lazy fetching in advance, and want to make sure that they are
 *  all instantiated asynchronously before you use it, the entity factory can help you with:
 *  
 *  	EntityManager.loadDeferred(srcObject, properties)
 *  
 *  where properties is a dot-notation, or a an array of dot-notation strings, or a comma separated list of
 *  don-notation paths. (e.g. "*", "assoc1.*", "assoc2.value") This returns a Deferred that will callback with the srcObject once the properties
 *  are loaded. This always returns a srcObject that no longer has any lazy properties (they have all been initialized).
 *  
 *  You can also force all lazy properties of an entity to be loaded with: (e.g. "*")
 *  
 *  	EntityManager.loadDeferred(srcObject)
 *  
 *  Which will return a deferred with all of its properties fetched (though the the associations or collections
 *  may contain sparsely populated entities. (If you want to be sure the collection entity properties or association properties
 *  are more fully populated, specify them with dot-notation in the properties list as described above).
 *  
 *  You can also access the loadDeferred methods from your SJO once it has been instrumented:
 *  
 *      myObjectInstance.loadDeferred(properties)
 *      
 *  Note that once you have performed a loadDeferred on an entity, you can use direct property access for 
 *  reading without consequence. For writing, you should still use the setters, since this will allow
 *  for the automatic dirty checking to occur.
 *  
 *  Constructor Sugar:
 *  After registering a class, the provided "alias" will be available as a constructor so (only if an alas has been provided)
 *      entityManager.registerClass("medryx.TestEntityClass", mapping, persistence, "TestEntityClass");
 *      
 *      var entity = new TestEntityClass()
 *      
 *      is the same as
 *      
 *      entityManager.buildItem("TestEntityClass")
 *      
 *      
 * @author shahma
 */
dojo.provide("medryx.orm.EntityManager");
dojo.require("medryx.assert");
dojo.require("medryx.exceptions.UnmanagedEntityClassException");
dojo.require("medryx.exceptions.IllegalArgumentException");
dojo.require("medryx.orm.internal.ManagedProperty");
dojo.require("medryx.orm.Identifier");
dojo.require("medryx.orm.Property");
dojo.require("medryx.orm.Association");
dojo.require("medryx.orm.Collection");
dojo.require("medryx.util.StringUtils");
dojo.require("dojo.DeferredList");
dojo.require("medryx.exceptions.EntityAlreadyExists");
dojo.require("medryx.exceptions.InvalidServerData");
dojo.require("medryx.orm.NamedQuery");
dojo.require("medryx.orm.internal.ClientSideQuery");

dojo.declare("medryx.orm.EntityManager", null, {
	LOADED_KEY:"_$LOADED$_",
    managedClasses:null,
	managedEntities:null,
    constructor:function(defaultPersistenceService) {
        this.managedClasses = {};
		this.managedEntities = {};
		this.defaultPersistenceService = defaultPersistenceService;
		this.aliases = {};
		this.queries = {};
    },
    
    /**
     * register a managed entity. if there is a mapping,
     * then it is a json mapping, and it will over-ride
     * any settings in the object itself. if there is
     * no mapping, then it is assumed the mapping is contained
     * within the entityClass itself
     * leaveConstructorAlone: if true, then don't instrument the constructor,
     * otherwise this will delegate construction to this manager
     * 
     */
    registerClass:function(entityClassName, mapping, persistenceService, alias) {
	
		if (dojo.isObject(arguments[0])) {
			var kwArgs = arguments[0];
			assert(assertion("kwArgs.entityClassName", "No entityClassName in registerClass"));
			this.registerClasses([kwArgs]);
		} else {
            this.registerClasses([{entityClassName:entityClassName, mapping:mapping, persistenceService:persistenceService, alias:alias}]);
		}
    },
    
    /**
     * see registerEntity
     * pass in an array of entityClass/mapping pairs
     * each entity has the following:
     * {
     *      entityClass,
     *      mapping: see above
     *      persistenceService: optional, implements PersistenceService for this entityClass,
     *      alias: a shortened name that will be synonumous to entityClass for this factory
     * }
     * @param {Object} entities
     */
    registerClasses:function(entities) {
        dojo.forEach(entities, function(properties) {
			var key = this.convertClassToKey(properties.entityClassName);
            var persistenceService = null;
		    var mapping = {};
			var queries = [];
			var identifierPropertyName = "id";
			var injection = {entityManager:this, parentClass:properties.entityClassName};
			var labels = [];
			if (!properties.mapping) { //create a mapping if from NATIVE
				
				//instantiate so we can look at the class
				var tempEntity = this.instantiateClassByName(properties.entityClassName);
				
				for (var prop in tempEntity) {
					if (tempEntity[prop] instanceof medryx.orm.internal.ManagedProperty) {
						//console.debug("mapping: " + prop);
						mapping[prop] = tempEntity[prop];
						mapping[prop].propertyName = prop;
						dojo.mixin(mapping[prop], injection);
						if (mapping[prop] instanceof medryx.orm.Identifier) {
							identifierPropertyName = prop;
						}
						if (mapping[prop].isLabel && mapping[prop].isLabel()) {
							labels.push(mapping[prop]);
						}
					} else {
						if (prop === "queries") {
							if (tempEntity[prop] && tempEntity[prop].length) {
								dojo.forEach(tempEntity[prop], function(query) {
									queries.push(query);
								});
							}
						}
					}
				}
				
				delete tempEntity;
			} else { //prepare a static mapping
			     var propertyMappings = properties.mapping;    
				for (var prop in propertyMappings) {
					if (prop === "queries") {
						dojo.forEach(propertyMappings[prop], function(query) {
                            queries.push(query);
                        });
					} else {
					
						var attributes = propertyMappings[prop].attributes;
	
						dojo.mixin(attributes || {}, injection);
						var propertyType = propertyMappings[prop].propertyType;
						if (dojo.isString(propertyType)) {
							propertyType = eval(propertyType);
						}
						mapping[prop] = new propertyType(attributes);
						mapping[prop].propertyName = prop;
						if (mapping[prop] instanceof medryx.orm.Identifier) {
	                        identifierPropertyName = prop;
	                    }
						if (mapping[prop].isLabel && mapping[prop].isLabel()) {
	                        labels.push(mapping[prop]);
	                    }
					}
				}
			}
			
			if (!properties.persistenceService) {
				assert(assertion("this.defaultPersistenceService", "Must provide either an entity persistenceService " +
				        "or a default persistenceService"));
                persistenceService = this.defaultPersistenceService;
			} else {
				persistenceService = properties.persistenceService;
			}
			
			if (!properties.alias) {
				properties.alias = properties.entityClassName;
			} else {
				this.createAliasConstructor(properties.entityClassName, properties.alias);
			}
			
			this.aliases[properties.alias] = properties.entityClassName;
            this.aliases[properties.entityClassName] = properties.entityClassName;
        
		    labels.sort(function(left, right) {
				return left.label - right.label;
			});
		
            this.managedClasses[key] = {mapping:mapping, persistenceService:persistenceService, identifierPropertyName:identifierPropertyName, labels:labels};
			dojo.forEach(queries, function(query) {
				this.registerQuery(properties.entityClassName, query.name, query.parameters);
			}, this);
			
        }, this);
		
    },
	
	registerQuery:function(entityClass, name, parameters) {
		
		var namedQuery =  new medryx.orm.NamedQuery(parameters);
		namedQuery.entityManager = this;
		namedQuery.entityClass = this.getClassName(entityClass);
		namedQuery.persistenceService = this.getPersistenceService(entityClass);
		this.queries[namedQuery.entityClass + "$" + name] = namedQuery;
		return namedQuery;
	},
	
	getRegisteredQuery: function(entityClass, name) {
		return this.queries[this.getClassName(entityClass) + "$" + name];
	},
	
	createAliasConstructor:function(entityClassName, alias) {
		var entityClass = eval(entityClassName);
		var entityConstructor = entityClass;
		var entityManager = this;
		var constructor = function() {
			entityConstructor.apply(this, arguments);
			var bag = {}
			if (arguments && arguments[0]) {
				bag = arguments[0];
			}
			return entityManager.constructEntity(this, bag);
		}
		dojo.mixin(constructor.prototype, entityConstructor.prototype);
		dojo.setObject(alias, constructor);
	},
	
	getLabelProperties:function(entityClass) {
		return this.getClassMetadata(entityClass).labels;
	},
	
    
	convertClassToKey:function(entityClassName) {
		var name = this.getClassName(entityClassName);
		var key = name;//.replace(/\./g, "_");
		return key;
	},
	
	isManagedClass:function(entityClass) {
		var className = this.getClassName(entityClass);
		return this.convertClassToKey(className) in this.managedClasses;
	},
	
    /**
     * @param {Object} instance
     */
    initializeManagedProperties:function(instance) {
        var mapping = this.getMapping(instance);
		//mixin, but don't overwrite existing value
		for (var prop in mapping) {
			var currentValue = instance[prop]
			if (typeof(currentValue) === 'undefined' || currentValue === null) {
				instance[prop] = mapping[prop];
			}
		}
    },
	
	getClassName:function(entityClass) {
		var className;
		if (dojo.isString(entityClass)) {
          className = this.aliases[entityClass] || entityClass;
        } else if (dojo.isFunction(entityClass)) {
		  className = entityClass.prototype.declaredClass;	//does this work for classes?
		} else if (dojo.isObject(entityClass)) {
            className = entityClass.declaredClass;
        }
		return className;
	},
	
	getClassMetadata:function(entityClass) {
		var className = this.getClassName(entityClass);
        var properties = this.managedClasses[this.convertClassToKey(className)];
        if (!properties) {
            throw new medryx.exceptions.UnmanagedEntityClassException(className + " in getClassMetadata (" + entityClass + ")");
        }
		return properties;
	},
	
	getMapping:function(entityClass) {
		return this.getClassMetadata(entityClass).mapping;
	},
	
	getPersistenceService:function(entityClass) {
		return this.getClassMetadata(entityClass).persistenceService;
    },
	
	instantiateClassByName:function(entityClass, bagOrId) {
		//first try to lookup in the managed entities
		
		var entityClassName = this.getClassName(entityClass);
		var instance = false;
		var instance = this.getManagedEntity(entityClassName, bagOrId);
		if (!instance) {
		  var entityClass = eval (entityClassName);
		  instance = new entityClass();
		 }
		
		 return instance;
	},
	
	/**
	 * @param {Object} instance
	 * @param {Object} prop
	 */
	getPropertyManager:function(instance, prop) {
		var mapping = this.getMapping(instance)
        return mapping[prop]; 
	},
	
	getIdentifierPropertyManager:function(instance) {
		return this.getPropertyManager(instance, this.getIdentifierProperty(instance));
	},
	
	getIdentifierProperty:function(instance) {
		return this.getClassMetadata(instance).identifierPropertyName;
	},
	
	getIdentifier:function(instance, initialize) {
		var prop = this.getIdentifierProperty(instance);
		
		var id = instance[prop];
		if (id instanceof medryx.orm.Identifier) {
			if (initialize) {
			     id = id.initialize(instance, prop);
            }
		} 
		
		if (!id) {
		  id = null;
		}
		return id;
	},
	
	setIdentifier:function(instance, id) {
        var idPropertyName = this.getIdentifierProperty(instance);
        instance[idPropertyName] = id;
	},
	
	/**
	 * pass in an initialized instance, and this will populate
	 * the properties appropriately
	 * @param {Object} instance
	 * @param {Object} bag
	 */
	populateInstance:function(instance, bag) {
		if (!bag) {
			return; //early exit
		}
		else if (!isNaN(bag)) {
			this.setIdentifier(instance, Number(bag));
		}
		else if (dojo.isObject(bag)){

			for (var prop in bag) {
			    var propertyManager = this.getPropertyManager(instance, prop);
			    if (propertyManager) {
				   propertyManager.initialize(instance, prop, bag[prop]);
			    } else {
				   console.info("received unmanaged property in bag: \"" + prop + "\" (ignoring)", instance, bag);
			    }
			}
		} else {
			throw new medryx.exceptions.IllegalArgumentException("invalid bag argument -- must be id|object|null|undefined");
		}
	},
    
	/**
	 * pass in an initialized instance and this will build
	 * the appropriate getters and setters.
	 * @param {Object} instance
	 */
	instrumentInstance:function(instance) {
		var mapping = this.getMapping(instance);
		var isReadOnly = true;
		for (var propertyName in mapping) {
			var pm = mapping[propertyName];
			
			if (pm) {
				if (!pm.isReadOnly()) {
					this.instrumentMutator(instance, pm);
					isReadOnly = false
				}
				this.instrumentAccessor(instance, pm);
				
			}
		}
		this.instrumentActiveRecord(instance);
		if (!isReadOnly) {
			this.instrumentDirtyChecking(instance);
		}
	},
	
	instrumentDirtyChecking:function(instance) {
        instance._dirty = false;
        instance.onDirty = function(property, oldValue, newValue){};
        instance.clean=function() {
            this._dirty = false;
            this._dirtyProperties = [];
        };
       
        instance.isDirty =function() {
            return this._dirty;
            
        };
        instance._dirtyProperties = [];
        
	},
	
	//Add "ActiveRecord" style management to the instance
	instrumentActiveRecord:function(instance) {
		instance.loadDeferred = dojo.hitch(this, "loadDeferred", instance);
		instance.save = dojo.hitch(this, "saveEntity", instance);
	},
	
	instrumentMutator:function(instance, propertyManager) {
		var setterName = "set" + medryx.util.StringUtils.capFirst(propertyManager.propertyName);
		
		//don't use an auto-getter if a getter is already defined (allows override)
		if (!dojo.isFunction(instance[setterName])) { 
        	instance[setterName] =dojo.hitch(this, "set", instance, propertyManager);
		}
		
		
	},
	
	/**
	 * save all changes to the server -- dirty entities and transient ones
	 */
	flush:function() {
		var defList = [];
		for (var key in this.managedEntities) {
			var def =  this.saveEntity(this.managedEntities[key]);
			if (def) {
				defList.push(def);
			}
		}
		return new dojo.DeferredList(defList);
	},
	
	getPersistentIdentifier:function(instance) {
		if (!this.isPersistentEntity(instance)) {
			return null;
		} else {
			return this.getIdentifier(instance);
		}
	},
	
	/**
	 * save the given instance with appropriate
	 * persister. if the instance is not dirty, 
	 * this is noop. returns a deferred or null
	 * if not dirty.
	 * 
	 * be sure to attach an errback to handle the case when
	 * your save fails.
	 * 
	 * if the instance identifier is null, then
	 * this is a new object to persist.
	 * 
	 * @param {Object} instance
	 */
	saveEntity:function(instance) {
		if ((instance.isDirty && instance.isDirty()) || !this.isPersistentEntity(instance)) {
			
		  var persister = this.getPersistenceService(instance);
		  var propertiesToPersist = {};
		  dojo.forEach(instance._dirtyProperties, function(propertyName) {
		       	propertiesToPersist[propertyName] = instance[propertyName];
		  });
		  var id = this.getPersistentIdentifier(instance);
		  var className = this.getClassName(instance);
		  
		  var deferred = persister.save(className, id, propertiesToPersist);
		  deferred.addCallback(dojo.hitch(this, function(result) {
		  	this.setIdentifier(instance, result);
			this.resetManagedEntityKey(instance);
			this.addManagedEntity(instance);
			instance.clean();
			return result;
		  }));
		  return deferred; //dojo.Deferred
	  }
	   return false; //boolean
	},
	
	deleteEntity:function(instance) {
		if (!this.isManagedEntity(instance)) {
			throw new medryx.exceptions.UnmanagedEntityClassException(instance + " in deleteEntity");
		}
		var id = this.getIdentifier(instance);
		if (this.isPersistentEntity(instance)) {
		  var persister = this.getPersistenceService(instance);
          var className = this.getClassName(instance);
          
          var deferred = persister.deleteItem(className, id);
          deferred.addCallback(dojo.hitch(this, function(result) {
            this.removeManagedEntity(instance);
			return result;
          }));
		  return deferred;
	   } else {
	   	   this.removeManagedEntity(instance);
		   var d = new dojo.Deferred();
		   d.callback();
		   return d;
	   }
	},
	
	isPersistentEntity:function(instance) {
		var id = this.getIdentifier(instance);
		var idPropertyManager = this.getIdentifierPropertyManager(instance);
		return idPropertyManager.isPersistentIdentifier(id);
	},
	
	//we implement getters as all being async, and just waiting for
	//them to come back if the synchronous call is made
	//if this is a performance hit, we could optimize it in the future
	//with a slight bit of complication
	instrumentAccessor:function(instance, propertyManager) {
	
		var getterName = "get" + medryx.util.StringUtils.capFirst(propertyManager.propertyName);
        var asyncGetterName = getterName + "Async";
	   
		//don't use an auto-getter if a getter is already defined (allows override)
		if (!dojo.isFunction(instance[getterName])) { 
        	instance[getterName] = dojo.hitch(this, "get", instance, propertyManager, false);
        }
		
		if (!dojo.isFunction(instance[asyncGetterName])) { 
        	instance[asyncGetterName] = dojo.hitch(this, "get", instance, propertyManager, true);
        }
		
		propertyManager.syncGetterName = getterName;
		propertyManager.asyncGetterName = asyncGetterName;
	},

    /**
     * event to monitor when a managed instance is made dirty
     * (we do not actually cache the old value, but could
     * if we wanted "revert" to work completely
     * 
     * @param {Object} instance
     * @param {Object} propertyName
     * @param {Object} oldValue
     * @param {Object} newValue
     */
    onDirty:function(instance, propertyName, oldValue, newValue) {
		
	},

    /**
     * called from the scope of the entity instance, so "this" is NOT the entity factory,
     * it is the entity instance!
     * @param {Object} propertyManager
     * @param {Object} value
     */
	set:function(instance, propertyManager, value) {
		if (propertyManager.isArray && propertyManager.isArray()) {
			if (!dojo.isArray(value)) {
				value = [value];
			}
		} else {
			if (dojo.isArray(value)) {
				if (value.length > 0) {
					value = value[0];
				}
			}
		}
		if (value != instance[propertyManager.propertyName]){ 
            instance._dirty = true;
			var oldValue = instance[propertyManager.propertyName];
			instance[propertyManager.propertyName] = value;
			instance.onDirty(propertyManager.propertyName, oldValue, value);
		   	this.onDirty(instance, propertyManager.propertyName, oldValue, value);
			instance._dirtyProperties.push(propertyManager.propertyName);
			return true;
        }
		return false;
	},


    /**
     * 
     * used by instrumented entities to get their property's values when the getter is called
     * @param {Object} propertyManager
     * @param {Object} asDeferred
     */
	get:function(instance, propertyManager, asDeferred) {
		var deferred;
		var propName = propertyManager.propertyName;
		//do we need to do some sort of optimization here?
		if (instance[propName] instanceof medryx.orm.internal.ManagedProperty) { 
			deferred = propertyManager.loadValueDeferred(instance, propName, !asDeferred); //synchronous if not asDeferred
			//when this comes back, check loaded state
			deferred.addCallback(dojo.hitch(this, function(instance) {
				this.initializeLoadState(instance);
				return instance;
			}, instance));
		} else {
			deferred = new dojo.Deferred();
			deferred.callback(instance[propName]);
			
			/*
			//optimize so we don't need these checks from here on in
			instance[propertyManager.syncGetterName] = function() {
				return instance[propName];
			}
			instance[propertyManager.asyncGetterName] = function() {
                var deferred = new dojo.Deferred();
                deferred.callback(instance[propName]);
				return deferred;
            }
            */
		}
        
		var defToReturn = new dojo.DeferredList([deferred]);
		if (asDeferred) { //ASYNC
            defToReturn.addCallback(dojo.hitch(instance, function() { //chain in a callback that will get the most recent value
                                              //anything added by the client will be added after this
			   return this[propName];
            }));
			return defToReturn;
		} else { //SYNC
			var valueReturned = false;
			
			defToReturn.addBoth(function(result) {
				valueReturned = true;
				return result; //keep the chain alive
			});
			
			while(!valueReturned); //BLOCKING -- just a failsafe. this is BAD. but the callback should have already been called, so this will just pass
			
			var finalValue = instance[propName];
			if (finalValue instanceof medryx.orm.internal.ManagedProperty) {
				throw new medryx.exceptions.InvalidServerData("the server agrees to be sure " + propName + " is initialized after a call to load, and it was not!");
			}
			return finalValue;
		}
		
	},

    getEagerProperties:function(instance) {
		var mapping = this.getMapping(instance);
        var eagerProperties = [];
        for (var propertyName in mapping) {
            var propertyType = mapping[propertyName];
            if (propertyType.isEager()) {
                eagerProperties.push(propertyType.propertyName);
            }
        }
		return eagerProperties;
	},
	
	initializeEagerFetchProperties:function(instance) {
		var eagerProperties = this.getEagerProperties(instance);
        if (eagerProperties.length > 0) {
			var def = this.loadDeferred(instance, eagerProperties, true);
			
			var returned = false;
			def.addBoth(function(res) {
				returned = true;
				return res;
			})
			
			while (!returned); //BLOCKING FOR EAGER FETCH, BAD, but ensures that this won't fail
		}		
	},
	
	/**
	 * 
	 * 
	 * @param {Object} instance
	 * @param {String | Array} properties -- "*" implies all properties, and is the default.
	 *         or pass a csv/array of property names, you can also you "." notation
	 *         to tell the server how deep to populate -- and use the "*" here as well
	 *         example: properties:["*", "association1.*", "association2.name"] 
	 */
	loadDeferred:function(instance, properties, sync) {
		if (!this.isPersistentEntity(instance)) {
			var d = new dojo.Deferred();
			d.callback(instance);
			return d;
		}
		var className = this.getClassName(instance)
		var persistenceService = this.getPersistenceService(className);
        
		properties = properties || "*"; //default is all properties
		var deferred = persistenceService.load(className, this.getIdentifier(instance), properties, sync);
		deferred.addCallback(dojo.hitch(this, function(instance, bag) {
			this.populateInstance(instance, bag);
			return instance;
		}, instance));
		deferred.addCallback(this, function(instance) {
            this.initializeLoadState(instance);
            return instance;
        }, instance);
		return deferred;
	},
	
	getManagedEntity:function(entityClassName, id) {
		
		if (id && dojo.isObject(id)) {
		    var idProp = this.getIdentifierProperty(entityClassName);
            id = id[idProp];
        } 
		if (!id) {
            return null;
        }
		
		var key = this.getManagedEntityKey(entityClassName, id);
		
		var entity =  this.managedEntities[key];
		
		return entity; 
	},
	
	getManagedEntityByKey:function(key) {
		return this.managedEntities[key]; 
	},
	
	isManagedEntity:function(instance) {
		try {
			var key = this.getManagedEntityKey(instance);
            return this.managedEntities[key] ? true : false;
		} catch (e) {
			return false;
		}
	},
	
	resetManagedEntityKey:function(instance) {
		//instance.__managedEntityKey = null;
	},
	
	_buildKey:function(instance, id, initialize) {
		var key = this.convertClassToKey(this.getClassName(instance));
        id = id || this.getIdentifier(instance, initialize);
        key = key + "$" + id;
		return key;
	},
	
	getManagedEntityKey:function(instance, id, initialize) {
		return this._buildKey(instance, id, initialize);
	},
	
	addManagedEntity:function(instance) {
		
		var key = this.getManagedEntityKey(instance, null, true);
		
		if (!this.managedEntities[key]) {
			this.managedEntities[key] = instance;
			
			if (!this.isPersistentEntity(instance)) {
			     this.onEntityAdded(instance);
			 }
		}
	},
	
	removeManagedEntity:function(instance) {
        var key = this.getManagedEntityKey(instance);
        if (this.managedEntities[key]) {
            this.managedEntities[key] = null;
        }
        
    },
	
	
	//iterate through the managed properties
	//for the instance and mark it as loaded
	//if they have all been initialzie
	initializeLoadState:function (instance) {
		var mapping = this.getMapping(instance);
		for (var prop in mapping) {
			if (instance[prop] instanceof medryx.orm.internal.ManagedProperty) {
				instance[this.LOADED_KEY] = false;
				return;
			}
		}
		//if we made it here, then we are completely loaded
		instance[this.LOADED_KEY] = true;
	},
	
	isCompletelyLoaded:function(instance) {
		return instance[this.LOADED_KEY] ? true : false;
	},
	
	   
    isPropertyLoaded:function(item, property) {
        //  summary: return true if item has an initialized property 
        //    i.e. hasAttribute(item, property) is true
        //    handles the * case and the dot-path case as well.
		//    Note that loaded simply means it is not lazy
		//    therefore "null", "false", "0" are considered LOADED
		var loaded = true;
        if (dojo.isArray(item)) {
			for (var i = 0; i<item.length; i++) {
				if (!this.isPropertyLoaded(item[i], property)) {
					return false; //EARLY EXIT 
				}
			}
			return true;
		} else if (property === "*") {
            loaded = this.isCompletelyLoaded(item);
        } else if (property.indexOf(".") > 0) { //dot-notation
        
            var matches = property.match(/(\w+)\.([\w\.]+)/);
		    if (matches && matches.length > 2) {
                 var associatedPath = matches[1];
				 
				 if (!item[associatedPath]) {
				 	loaded = true; //null is actually an initialized value
				 
				 } else if (this.isPropertyLoaded(item, associatedPath)) {
				 
				 	var subPath = matches[2];
				    loaded = this.isPropertyLoaded(item[associatedPath], subPath);
				
				 } else {
				 	loaded = false;
				 }
             }
        } else {
            if (this.isCompletelyLoaded(item)) { //efficient shortcut
                loaded = true; //shortcut that has already done the checking for every property
            } else {
	            loaded = !(item[property] instanceof medryx.orm.internal.ManagedProperty); //return fals if Managed still
            }
        
        }
		
		return loaded;
    },
	
	//this is called regarless only if the added entity is added 
	//that is TRANSIENT
	onEntityAdded:function(instance) {
		
	},
	
	query:function(/* function | string | alias */entityClass, /* object */queryArgs, /* string? */namedQuery, /*Boolean?*/ sync, /* Boolean? */forceReload) {
		//  summary:
		//    execute a query for the given entityClass and return a deferred that will callback with the results
		//  description:
		//    Queries can be either "server" queries, or "client" queries.
		//    A client query is executed against a cahce of entities
		//    that was obtained via the use of a "registered" query. This can
		//    be very efficient, with no server calls required to sort, filter and
		//    paginate your data, assuming that it is not too inefficient to 
		//    load all the results of the registered query into the cache to begin
		//    with. 
	    //
		//    If, on other hand, your query returns a very large number of results
		//    and requires server-side pagination, then it is suggested that you 
		//    do NOT use the client queries above. We do cache the results
		//    of even these server queries, so that an _identical_ repeat query would
		//    simply use the cache (unless forceReload = true). But by and large, you
		//    should expect that each time a server query is executed, it will make
		//    a request to the server for data.
		//
		//    You make the distinction between a server query and client query by including
		//    the namedQuery parameter. If the namedQuery maps to a
		//    previously registered query, than this is a client-side query to be performed
		//    against the registered query's complete results. If no namedQuery is
		//    provided, then this is essentially a passthrough to the persistenceService's
		//    fetch method, with some handling of caching as described above.
	    //  entityClass: the class that is being queried for
		//  queryArgs:
		//  {
		//    query:query-object,
		//    queryOptions: object,
		//    start: int,
		//    count: int,
		//    sort:array
		//  }
	    //    *query* parameter: 
		//      Similar to dojo.data.ReadApi. However for the default implementation
		//      of client-side query, this should be an array of propertyName/value 
		//      pairs. the value may be a string (with wildcards), or an array of strings
		//      (that are "or'd" together)
		//    *queryOptions* parameter:
		//      Similar to dojo.data.ReadApi. Supports ignoreCase for client-side queries.
		//      Should support "properties" for server side queries to give the server
		//      a hint about what properties should be returned for this query
		//      for each entity
		//    *start*
		//    *count*
		//    *sort*
		//      In its simplest case, an array of string property names. Default client side sorting
		//      supports an arry of objects with attribute (string), descending (boolean), 
		//      comparator (function), and getter (function). If your sort array supplies a custom
		//      getter or comparator, then your results will be sorted on the client when before
		//      they are returned.
		//
		//  namedQuery: the name of a registered query against which this client-side query should be made
		//  sync: guarantees that the returned deferred has already been "called back" prior to returning.
		//  forceReload: ignore the cache and reload all entities
        var entityClass = this.getClassName(entityClass);
		var registeredQuery = this.getRegisteredQuery(entityClass, namedQuery);
		console.debug("query", namedQuery, entityClass);
		if (registeredQuery) {
			return registeredQuery.executeQuery(queryArgs, sync, forceReload); //dojo.Deferred
		}
		
		//otherwise its a server query
		
        var queryOptions = queryArgs.queryOptions;
        var persistenceService = this.getPersistenceService(entityClass);
        
		var sort = queryArgs.sort;
		
        //TODO: ADD IN CACHE STUFF
        var deferred  = persistenceService.fetch(entityClass, {
            query:queryArgs.query,
            queryOptions:queryArgs.queryOptions,
            start:queryArgs.start,
            count:queryArgs.count,
            sort:sort,
        }, queryArgs.sync);
        
        deferred.addCallback(dojo.hitch(this, function(obj) {
            var items = obj.items;
            var entities = dojo.map(items, function(item) {
                return this.buildEntity(entityClass, item);
            }, this);
            return entities;
        }));
		
		if (sort && sort.length) {
			if (dojo.some(sort, function(field) { return field.comparator || field.getter})) {
				deferred.addCallback(function(entities) {
					medryx.orm.internal.ClientSideQuery.sortList(entities, sort);
					return entities;
				});
			}
		}
		
        return deferred;
	},
	
	initializeEntity:function(entity, bag) {
		this.initializeManagedProperties(entity);
        
        //build our getters/setters
        
        this.instrumentInstance(entity);
        
        //now we have an instrumented instance, populate it
        
        this.populateInstance(entity, bag);
        
        //handle eager fetches
        
        this.initializeEagerFetchProperties(entity);
        
        //set the state of the entity as loaded or not (now that eager fetching is done and we are completely initialized)
        
        this.initializeLoadState(entity);
        
        //add to our cache of entities
        
        this.addManagedEntity(entity);
	},
	
	/**
	 * called from a constructor of a managed class.
	 * if this is already a managed entity, return that
	 * entity, otherwise, return the passed entity
	 * @param {Object} entityClassName
	 * @param {Object} bagOrIdentifier
	 */
	constructEntity:function(entity, bagOrIdentifier) {
		assert(assertion("this.isManagedClass(entity)", "entity must be a managed class in constructEntity"));
		  
        var existingInstance = this.getManagedEntity(this.getClassName(entity), bagOrIdentifier);
        if (!existingInstance) {
		 	//perhaps the id is already set on the entity. look there
			existingInstance = this.getManagedEntity(this.getClassName(entity), this.getIdentifier(entity));
		 }
        if (existingInstance) {
			entity = existingInstance;
		}        
		
        if (entity.isDirty && entity.isDirty()) {
            console.warn("Creating an instance that was already modified and marked dirty without flushing. This will overwrite previous changes!", entity);
            //throw new medryx.exceptions.EntityAlreadyExists("cannot build entity:" + entityClassName + " because it was already built and is already dirty, flush first");
        }
        
        this.initializeEntity(entity, bagOrIdentifier);
        
        return entity;
    },
	
	/**
	 * buld an entity of the passed className initialize with the bag's properties
	 * 
	 * //TODO when building a new entity, don't initialize managed properties except for generating an id
	 * //since lazy loading would not be applicable (or at lease make lazy load fail
	 * //if its a transient entity)
	 * @param {String} entityClassName
	 * @param {Object} bag | id
	 */
    buildEntity:function(entityClassName, bagOrIdentifier) {
		assert(assertion("this.isManagedClass(entityClassName)", "entity must be a managed class in buildEntity"));

		var entity = this.instantiateClassByName(entityClassName, bagOrIdentifier);
		
		if (entity.isDirty && entity.isDirty()) {
			console.warn("Loading an instance that was already modified and marked dirty without flushing. This will overwrite previous changes!", entity);
			//throw new medryx.exceptions.EntityAlreadyExists("cannot build entity:" + entityClassName + " because it was already built and is already dirty, flush first");
		}
		
		this.initializeEntity(entity, bagOrIdentifier);
		
        return entity;
    }
});

